﻿using codeparser.net.v2.Aspects;
using codeparser.net.v2.ConfigurationItems;
using codeparser.net.v2.Contracts;
using codeparser.net.v2.Entities;
using codeparser.net.v2.Exceptions;
using codeparser.net.v2.Lexer;

using configuration.net;

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Xml;
using System.Globalization;

namespace codeparser.net.v2
{
    /// <summary>
    /// Represents the parser.
    /// </summary>
    public class Parser : IParser
    {
        /// <summary>
        /// Contains the 
        /// </summary>
        private Dictionary<Type, bool> _throwExceptionTable;

        /// <summary>
        /// Gets or sets the tag configuration.
        /// </summary>
        /// <value>The tag configuration.</value>
        public TagConfiguration TagConfiguration
        {
            get;
            set;
        }

        /// <summary>
        /// Contains the parser options.
        /// </summary>
        private ParserOptions _options;

        /// <summary>
        /// Initializes a new instance of the Parser type.
        /// </summary>
        /// <param name="tagConfiguration">The tag configuration.</param>
        [LoggingAspect]
        public Parser(TagConfiguration tagConfiguration)
            : this(tagConfiguration, new ParserOptions())
        {
        }

        /// <summary>
        /// Initializes a new instance of the Parser type.
        /// </summary>
        /// <param name="reader">The XML reader that contains the configuration.</param>
        [LoggingAspect]
        public Parser(XmlReader reader)
        {
            try
            {
                // Get the configuration.
                XmlDocument document = new XmlDocument();
                document.Load(reader);

                // Check the version of the configuration file. If it is lower than the version of
                // this assembly, use it, otherwise throw an exception.
                Configuration configuration = new Configuration(document);
                Version configurationVersion =
                    new Version(configuration.Required<CodeparserNet>().Value<Version>().AsString());
                Version assemblyVersion =
                    Assembly.GetExecutingAssembly().GetName().Version;
                if (configurationVersion > assemblyVersion)
                {
                    throw new ConfigurationFileVersionNotSupportedException(String.Format(CultureInfo.CurrentCulture,
                        "Configuration version '{0}' is newer than assembly version '{1}'", configurationVersion.ToString(), assemblyVersion.ToString()));
                }

                // Initialize this instance.
                this.Init(TagConfiguration.Load(document), ParserOptions.Load(document));
            }
            catch (CodeparserException ex)
            {
                this.HandleCodeparserException(ex);
            }
        }

        /// <summary>
        /// Initializes a new instance of the Parser type.
        /// </summary>
        /// <param name="tagConfiguration">The tag configuration.</param>
        /// <param name="options">The parser options.</param>
        [LoggingAspect]
        public Parser(TagConfiguration tagConfiguration, ParserOptions options)
        {
            // Initialize this instance.
            this.Init(tagConfiguration, options);
        }

        /// <summary>
        /// Initializes this instance.
        /// </summary>
        /// <param name="tagConfiguration">The tag configuration.</param>
        /// <param name="options">The parser options.</param>
        [LoggingAspect]
        private void Init(TagConfiguration tagConfiguration, ParserOptions options)
        {
            // Initialize the fields.
            this.TagConfiguration = tagConfiguration;
            this._options = options;

            this._throwExceptionTable = new Dictionary<Type, bool>();
            this._throwExceptionTable.Add(typeof(InvalidAttributeException), this._options.ThrowInvalidAttributeExceptions);
            this._throwExceptionTable.Add(typeof(InvalidCharacterException), this._options.ThrowInvalidCharacterExceptions);
            this._throwExceptionTable.Add(typeof(InvalidTagException), this._options.ThrowInvalidTagExceptions);
            this._throwExceptionTable.Add(typeof(InvalidTagNestingException), this._options.ThrowInvalidTagNestingExceptions);
            this._throwExceptionTable.Add(typeof(MissingClosingTagException), this._options.ThrowMissingClosingTagExceptions);
            this._throwExceptionTable.Add(typeof(MissingOpeningTagException), this._options.ThrowMissingOpeningTagExceptions);
            this._throwExceptionTable.Add(typeof(RequiredAttributeMissingException), this._options.ThrowRequiredAttributeMissingExceptions);
            this._throwExceptionTable.Add(typeof(UnknownAttributeValueStyleException), this._options.ThrowUnknownAttributeValueStyleExceptions);
            this._throwExceptionTable.Add(typeof(RequiredAttributeValueMissingException), this._options.ThrowRequiredAttributeValueMissingExceptions);
            this._throwExceptionTable.Add(typeof(NeedlessAttributeValueSpecifiedException), this._options.ThrowNeedlessAttributeValueSpecifiedExceptions);
            this._throwExceptionTable.Add(typeof(MissingRuleException), this._options.ThrowMissingRuleExceptions);
            this._throwExceptionTable.Add(typeof(TagNotFoundException), this._options.ThrowTagNotFoundExceptions);
            this._throwExceptionTable.Add(typeof(LanguageNotSupportedException), this._options.ThrowLanguageNotSupportedExceptions);
        }

        /// <summary>
        /// Parses the specified text.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <returns>The parsed text.</returns>
        [LoggingAspect]
        public string Parse(string text)
        {
            // Create an instance of the lexer.
            Lexer.Lexer lexer = new Lexer.Lexer(this.TagConfiguration, this._options.TokenValidatorStrategy);

            // Convert the input text to tokens.
            IList<Token> tokens;
            try
            {
                tokens = lexer.Tokenize(text);
            }
            catch (CodeparserException ex)
            {
                this.HandleCodeparserException(ex);
                return text;
            }

            // Create an instance of the analyer.
            Analyzer.Analyzer analyzer = new Analyzer.Analyzer(this._options.TokenValidatorStrategy, this.TagConfiguration);
            ITreeNode parseTree;
            try
            {
                parseTree = analyzer.CreateParseTree(tokens);
            }
            catch (CodeparserException ex)
            {
                this.HandleCodeparserException(ex);
                return text;
            }

            // Create the parsed text.
            string parsedText = parseTree.ToString();

            // Return the parsed text to the caller.
            return parsedText;
        }

        /// <summary>
        /// Handles the specified exception of type CodeparserException.
        /// </summary>
        /// <param name="ex">The exception.</param>
        [LoggingAspect]
        private void HandleCodeparserException(CodeparserException ex)
        {
            // If the exception table has not been initialized, simply throw the exception.
            if (this._throwExceptionTable == null)
            {
                throw new CodeparserException("An internal error occured", ex);
            }

            // If the exception has not been registered, simply rethrow it.
            if (!this._throwExceptionTable.ContainsKey(ex.GetType()))
            {
                throw ex;
            }

            // Check whether the exception should be thrown. If so, throw an exception of type
            // CodeparserException.
            if (this._throwExceptionTable[ex.GetType()])
            {
                throw new CodeparserException("An internal error occured", ex);
            }
        }

        /// <summary>
        /// Save the parser configuration to the specified XML writer.
        /// </summary>
        /// <param name="writer">The XML writer.</param>
        [LoggingAspect]
        public void SaveConfiguration(XmlWriter writer)
        {
            // Write the document.
            writer.WriteStartDocument();
            {
                // Write the root node.
                writer.WriteStartElement("CodeparserNet");
                {
                    // Write the version information.
                    writer.WriteAttributeString("Version",
                        Assembly.GetExecutingAssembly().GetName().Version.ToString());

                    // Save the tag configuration.
                    ((IXmlWriter)this.TagConfiguration).Save(writer);

                    // Save the parser options.
                    ((IXmlWriter)this._options).Save(writer);
                }
            }
            writer.WriteEndDocument();
        }
    }
}